//Serão consideradas palavras apenas as sequencias de caracteres compostas por letras.
//Os caracteres " ,./?'\";:|^-!$#@`~*&%)(+=_}{][\n\t\\" serão considerados separadores pela funcao strtok
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <ctype.h>
#include <mpi.h>
#include <omp.h>

//Definicao do tamanho de cada palavra e de cada bloco de leitura
#define BLOCK_SIZE 300
#define WORD_SIZE 100

void strRev(char *);
void strUpper(char *);
int chkPal(char *);
int crivo(char *);

int main(int argc, char *argv[])
{
	//Variáveis
	int cont = 0, cont2 = 0, i, n = 0;
	double t0, tf, nT = 0.0, wT = 0.0, rT = 0.0;
	char buffer[BLOCK_SIZE], **palavras;
	FILE *fpin;
	long fileSize=0;
	
	int rank, source = 0, dest = 0, rc;
	
	//Registros de resultados dos nos
	long a[19]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
	long b[19]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
	
	long a2[19]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
	long b2[19]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
	
	double a3[19]={0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0};
	double b3[19]={0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0};
	
	double a4[19]={0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0};
	double b4[19]={0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0};
	
	//Abre o arquivo e vai até o final para determinar o tamanho
	if((fpin=fopen(argv[1], "r")) != NULL)
	{
		fseek(fpin, 0, SEEK_END);
		fileSize = ftell(fpin);
	}
	else
	{	
		return -1;
	}
	fclose(fpin);

	// MPI_Init(&argc, &argv)
	// Inicializa uma sessão MPI. 
	rc = MPI_Init(&argc, &argv);
	if (rc == MPI_SUCCESS)
	{
		//Obtem ID do processo na variável rank	
		MPI_Comm_rank(MPI_COMM_WORLD, &rank);

		//Pega numero de processos que irao rodar para fazer a distribuicao de tarefas
		int nProc;
		MPI_Comm_size(MPI_COMM_WORLD, &nProc);
			
		//Nos que irão fazer leitura do arquivo
		if ((rank >= 1) && (rank <= (nProc-1)))
		{
			//Abre o arquivo
			if((fpin=fopen(argv[1], "r")) == NULL)
			{	
				printf("Erro ao abrir o arquivo %s.\n", argv[1]);
				MPI_Finalize();
			}
			
			//Abre o arquivo onde serão registrados os palindromos
			FILE *fpout;
			char outFileName[50];
			sprintf(outFileName,"%d.txt",rank);
			if((fpout=fopen(outFileName, "w")) == NULL)
			{	
				printf("Erro ao abrir o arquivo %s.\n", outFileName);
				MPI_Finalize();
			}
			
			//Posiciona cada um dos nos em suas respectivas posicoes no arquivo
			long iter = 0;
			long nodeOffset = (rank-1) * BLOCK_SIZE; //No 1 = 0, No 2 = 300, No 3 = 600...
			long seekPosition = nodeOffset + (iter * (nProc-1) * BLOCK_SIZE) - 1;
			
			fseek(fpin, seekPosition, SEEK_SET);
			
			while(!feof(fpin))
			{	
				//Le bloco
				fread(buffer, 1, BLOCK_SIZE,fpin);
				char* buffer2 = strdup(buffer);
				
				//Parseia o bloco, separando em palavras
				char* token = strtok(buffer, " ,./?'\";:|^-!$#@`~*&%)(+=_}{][\n\t\r\\");
				while(token != NULL)
				{
					char* aux = strdup(token);
					t0 = omp_get_wtime();
					if (chkPal(aux))
					{
						tf = omp_get_wtime();
						wT += tf - t0;
						cont++;
						fprintf(fpout,"%s", aux);
						t0 = omp_get_wtime();
						if(crivo(aux))
						{
							tf = omp_get_wtime();
							nT += tf - t0;
							cont2++;
							fprintf(fpout,"   eh primo!!!");
						}
						fprintf(fpout,"\n");
					}
					token = strtok(NULL, " ,./?'\";:|^-!$#@`~*&%)(+=_}{][\n\t\r\\");
				}
				
				//Prepara a posicao que sera feita a proxima leitura
				iter++;
				seekPosition = nodeOffset + (iter * (nProc-1) * BLOCK_SIZE);
				if (seekPosition > fileSize) //Testa se o proximo seek vai pra fora do arquivo
				{
					break;
				}
				else
				{
					fseek(fpin, seekPosition, SEEK_SET); //Pula para o proximo lugar que sera lido
				}
			}
			//Quando o no processou sua parte do arquivo, popula os resultados
			a[rank] = cont;
			a2[rank] = cont2;
			a3[rank] = wT;
			a4[rank] = nT;
			fclose(fpout);
			fclose(fpin);
		}
		
		if (rank == 0)
		{
			//Quando o no mestre comeca, marcar o tempo
			t0 = omp_get_wtime();
		}
		
		//Todos os nos rodarao (no root ira fica bloqueado ate receber informacoes dos nos leitores).
		MPI_Gather(&(a[rank]), 1, MPI_LONG, b, 1, MPI_LONG, 0, MPI_COMM_WORLD);
		MPI_Gather(&(a2[rank]), 1, MPI_LONG, b2, 1, MPI_LONG, 0, MPI_COMM_WORLD);
		MPI_Gather(&(a3[rank]), 1, MPI_DOUBLE, b3, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD);
		MPI_Gather(&(a4[rank]), 1, MPI_DOUBLE, b4, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD);
		
		if (rank == 0)
		{
			//Tempo final (depois que o no mestre recebe todas os resultados dos nos leitores
			tf = omp_get_wtime();
			int q = 0;
			long pa = 0, pr = 0;
			printf("Tempo levado por cada no para processar:\nPalindromo\t\tPrimo\n");
			for (q = 1 ; q < nProc ; q++)
			{
				printf("%lf\t\t%lf\n",b3[q],b4[q]);
				pa += b[q];
				pr += b2[q];
			}
			printf("Tempo: %lf\nPalindromos: %ld\nPrimos: %ld\n", (tf-t0), pa, pr);
		}
	}
	MPI_Finalize( );
	return 0;
}

void strRev(char *str)
{
	//Baseado no algoritmo XOR SWAP
	// X := X XOR Y
	// Y := X XOR Y
	// X := X XOR Y
	char *p1, *p2;
	for (p1 = str, p2 = str + strlen(str) - 1; p2 > p1; ++p1, --p2) //p1 vai do começo até o meio da string e p2 vai do fim até o meio
	{
		//Swap
		*p1 ^= *p2;
		*p2 ^= *p1;
		*p1 ^= *p2;
	}
}

int chkPal(char *str)
{
	char aux[WORD_SIZE], aux2[WORD_SIZE];
	//Inverte a palavra. Se for igual a palavra nao invertida, e palíndromo
	if(strlen(str) > 1)
	{
		strcpy(aux,str);
		strcpy(aux2,str);
		strUpper(aux2);
		strRev(aux2);
		if(strcmp(aux2,aux) == 0 )
		{
			return 1;
		}
		else
		{
			return 0;
		}
	}
	else
	{ 
		return 0;
	}
}

void strUpper(char *str)
{
	//Passa a palavra para maiuscula, eliminando acentos, etc.
	int i;
	for (i = 0 ; i < strlen(str) ; i++)
	{
		str[i] = toupper(str[i]);
	}
}

int crivo(char palavra[])
{
	//Aplicação do crivo de erastotenes sobre as somas dos valores ASCII das palavras
	int i, divisor, divisores, num=0;
	float max;

	for (i=0;i<strlen(palavra);i++) 
	{
		num += (int)palavra[i];
	}

	if(num == 1 || num == 2 || num == 3)
	{
		return 1;
	}
	else if(num % 2 == 0)
	{
		return 0;
	}
	else
	{
		divisor = 2;
		divisores = 0;
		max = sqrt(num);
		while(divisor <= max && divisores == 0)
		{
			if(num % divisor == 0)
			{
				divisores++;
			}
			divisor++;
		}
		if(divisores != 0)
		{
			return 0;
		}
		else
		{
			return 1;
		}
	}
}
